package org.fjsei.yewu.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.TransientDataAccessException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.UndeclaredThrowableException;
import java.sql.SQLException;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;


/** 这里逻辑真麻烦，各种可能性。
 * CRDB数据库对CPU要求相当的高! 查询实际有自带缓存的能力；并发争用问题，死锁，IO性能要求高，网络延迟和延迟时间波动性要求。
 * Raft分布式一致性算法，节点有三种角色 Leader：Candidate：Follower。
 * 为何选择小强数据库的？看上其扩展性支持水平线性扩展，磁盘损坏灾难恢复能力，分布式事务强一致性，可用SSD存储介质，数据分片无需应用层关心。
 * 可支持最大4EB数据，支持跨地域部署，去中心化，自动水平扩展，自动故障恢复；CockroachDB选择了range base分片方式，在线扩容。
 * 华为云数据库 GaussDB 与之共同点：share nothing架构，无锁并发事务，etcd集群基于raft；不同点：单分片版本源代码开源，支持4PB存储。
 * 云原生分布式数据库 CockroachDB 小强 CRDB问题：
 * 三个节点宕机2个后无法继续死等恢复；
 * url连接必须指定某个IP和端口的，生产环境：端口不一样变，IP域名也不能动，配置特定指定一个节点宕机就无法访问。
 * 开源数据库用GoLang写的,Raft： https://github.com/cockroachdb/cockroach
 必须写上ssl证书路径名字；192.168.171.3:26257实际是负载均衡器地址。
 url: jdbc:postgresql://192.168.171.3:26257/seipf?ssl=true&sslmode=verify-full&sslrootcert=D:/ca.crt&sslcert=D:/client.herzhang.crt&sslkey=D:/client.herzhang.pk8&sslpassword=
 * 可跨多个集群部署a single, multi-region CockroachDB cluster running on Kubernetes.； private Kubernetes cluster；
 * Manual GCE:自己弄manual GCE cluster 谷歌的GCE云服务器； Hosted GKE:是商业云服务，GKE cluster云服。
 * win10 virtualbox 安装kubernetes集群； Centos7.6搭建k8s环境；running CockroachDB on Kubernetes: # Linux kernel ；
 * k8s集群 Kubernetes； Operator；--listen-addr 默认，配置--advertise-addr= ；
 * Use SSDs instead of traditional HDDs. 不建议使用网络连接的块存储。
 * Deploy CockroachDB manually or use an orchestration system like Kubernetes生产环境, Start a cluster locally测试。
 * Deploy CockroachDB On-Premises 本地。 TCP 26257  8080 单机器运行1节点； multiple 机房吗availability zones: 跨地域 multiple regions:；
 * 单个zone可能有多个node； NTP service； using load balancer, 多个负载平衡器实例 HAProxy；
 * using systemd. #数据库部署支持： Kubernetes部署模式（k8s）， Deploy CockroachDB On-Premises人工集群部署模式(linux命令+load balancer配置文件)。
 * 数据分片是自动进行，没有路由的说法，按照字段实际取值调整, 列存K/V存储？。
 * 大多数用户不需要直接使用分区(企业版)。应使用CockroachDB内置的多区域功能，自动处理分区。地理分区,归档分区;涉及主键(range+id)二级索引。configure non-partitioned table to be GLOBAL table.
 * PARTITION BY LIST (country)(, ,); PARTITION BY RANGE (graduation_date)(PARTITION graduated VALUES FROM (MINVALUE) TO ('2017-08-15'),PARTITION current VALUES FROM ('2017-08-15') TO (MAXVALUE));
 * Sharding 分片：CockroachDB自动切分数据，作为其分布式数据库体系结构的一部分。
 * 默认是Zone failure幸存。用以下语句开启Region failure幸存，代价是以较慢的写入性能（由于网络跳数 @TCP延迟@）,写入延迟将至少增加到最近区域Region的来回传输时间,读取性能不受影响;最少配3个Region。
 * 禁用Linux内存交换，--cache=.35 --max-sql-memory=.35合计最多75%RAM; --cache默认128MB。
 * CockroachDB总用可序列化隔离级别，READ COMMITTED若是没有其他措施配套的话，会导致并发导致的逻辑错误。
 * ACIDRain：发现事务Bug不严谨； 小强数据库只提供SERIALIZABLE隔离级别的：     https://www.cnblogs.com/ivan-uno/p/8257672.html
 * 普通隔离级别RC 读已提交 级别存在 write skew 问题，是漏洞，所以不能使用的。 乐观锁防止更新丢失问题，但是 write skew 不是同一个漏洞的，是逻辑前提条件不可重复读两次读判定相反引起的，提交也不是同一条记录更新。
 * This is why we recommend keeping transactions as small as possible.
 * IDEA Database工具窗开启连接：可能导致CRDB事务运行速度爆缓慢，要掐掉连接。客户连接CRDB集群也直接针对单个Database的。
 * 【限制】JPA也不能跨Database吧; 跨越Database界线在DB的Cluster内做外键关联：被CRDB明确禁止。sql.cross_db_fks.enabled默认false; 定位不同:Schema层次。
 * 问题编程模型变化大，事务，争用激烈，要求小事务化，非索引字段不要用于过滤用途避免全面扫描。  https://www.cockroachlabs.com/docs/stable/transactions.html
 * JPA Hibernate家族实际上对 CockroachDB 当做特例处理，有别于传统关系数据库的。 NewSQL; 分布式。
 * */

@Component
@Aspect
@Order(Ordered.LOWEST_PRECEDENCE - 1)
public class RetryableTransactionAspect {
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    //极端情况允许对前端透明的 自动 尝试次数。
    private final int retryAttempts = 20;     //太多也没用，小事务retryAttempts可以设置大一些，大的长时间事务应该设置小的。


    /**
     * AOP的基本概念 Pointcut(切入点) @Aspect(切面); Advice @Around： 环绕增强，相当于MethodInterceptor.
     * execution：用于匹配方法执行的连接点； @annotation：用于匹配当前执行方法持有指定注解的方法；
     * 例子 @AfterReturning(pointcut="execution(* com.abc.service.*.access*(..)) && args(time, name)", returning="returnValue")
     * 例子@Pointcut("@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController)")
    只要有可能，CockroachDB将在不通知客户端的情况下在内部自动重试事务。当CockroachDB无法在没有客户端干预的情况下自动解决错误时，它只会向客户端发送序列化错误。
     in this case, any method in the io.roach. namespace =类包路径; org.fjsei.yewu
     最早是： @Pointcut("execution(* org.fjsei..*(..)) && @annotation(transactional)")  必须是注解了@Transactional的函数；
    * 下面@Around(value = "anyTransactionBoundaryOperation哪一个函数才是处理主体，这个是注解撕开个口子，前置环节，可能允许关联多个多种处理函数。
    * */
    @Pointcut("(execution(* md..*(..)) || execution(* org.fjsei.yewu..*(..)) ) && @annotation(transactional)")
    public void anyTransactionBoundaryOperation(Transactional transactional) {
    }


    /**数据库CRDB带来特别处理机制 : contention; client-side retry handling.
     * 底层切入点捕捉，配置参数问题： https://www.cockroachlabs.com/docs/v21.2/build-a-spring-app-with-cockroachdb-jpa?filters=local
     * 配套给 @Pointcut（）;  函数参数字段名 "pjp,transactional"
     * 其它@Transactional注解的若不是CRDB数据库管理的也可被截获，也没有毛病。 AnnotationInvocationHandler
     * 【重要特征：自定义】label={"noRetry"} 禁止启用重试机制标签: 有些事务不适合这里的机制，可能不是CRDB数据库。
     * 事务冲突时刻有可能一方取消了，也可能双方都被取消了。
     * 配置：graphql.servlet.async.timeout尽量设置长些的。而Transactional(timeout=)针对重要函数尽量设置timeout最好小点用户忍受程度的时间等待。事务默认不会超时的。
     * */
    @Around(value = "anyTransactionBoundaryOperation(transactional)",
            argNames = "pjp,transactional")
    public Object doInTransaction(ProceedingJoinPoint pjp, Transactional transactional)
                     throws Throwable {
        final Instant callTime = Instant.now();
        boolean noRetry= Arrays.asList(transactional.label()).contains("noRetry");
        if(noRetry)   return  pjp.proceed();      //不是CockroachDB不需要重试事务
        final int timeout=transactional.timeout();      //程序上限定可以忍受多久#死等到干完了才报错时间超时了，白干了
        //long myrunno= runno++;    //数据库事务调度导致的重复运行期间，一次代码请求有个标识编码myrunno。
        long backoffMillis=0;
        int numCalls = 0;    //重新发动事务请求=再次运行函数，失败事务已尝试次数。

        do {
            try {
                numCalls++;
                if(numCalls>1) {
                    try {
                        if(numCalls>2) {
                            Duration duration = Duration.between(callTime, Instant.now());
                            if (timeout > 0 && timeout < (duration.getSeconds() + (backoffMillis / 1000)) )
                                break;       //超时放弃
                            //实际上pjp.proceed()还是死等的一致等到数据库干完也不管timeout,=还是会马后炮的，死等到干完了才报错时间超时了，白干了。
                        }
                        Thread.sleep(backoffMillis);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                //这里实际代表了本事务波及的函数再次运行。 必须是整个一个函数都能够回撤和重复运行，【特别注意】不能够回滚重复执行的代码不要和CRDB数据库事务写在一个函数里面！！！
                Object rv = pjp.proceed();
                //这里抛出异常；正常完成事务的才会接着运行下面这句的。
                if (numCalls > 1) {
                    //原来logger.debug ； 有失败到成功 浪费时间挺大："PT1.06S“。 Thread.sleep多线程？前后端接口RestController发起等待。
                    //@Transactional设置错误：导致不会产生numCalls>=2 临时错误已尝试 18 of 30 retries (PT1M33.5076041S)
                    logger.debug(
                            "完成,做" + numCalls + "次 of " + retryAttempts + "最多 ("
                                    + Duration.between(callTime, Instant.now()).toString() + ")");
                }
                return rv;
            } catch (UndeclaredThrowableException ex) {     //实质上没运行到这？
                Throwable t = ex.getUndeclaredThrowable();
                while (t instanceof UndeclaredThrowableException) {         //为啥？？
                    t = ((UndeclaredThrowableException) t).getUndeclaredThrowable();
                }
                Throwable cause = NestedExceptionUtils.getMostSpecificCause(ex);
                if (cause instanceof SQLException) {
                    SQLException sqlException = (SQLException) cause;
                    if ("40001".equals(sqlException.getSQLState())) { // Transient error code 小强数据库临时错误可以再试
                        backoffMillis=handleTransientException(sqlException, numCalls, pjp.getSignature().toShortString());
                        continue;
                    }
                }
                throw ex;
            } catch (TransientDataAccessException | TransactionSystemException  ex) {
                //厂家给的是TransientDataAccessException | TransactionSystemException  不可加| JpaSystemException 导致不能抛出异常直接第二尝试当做成功返回。
                //jpa.JpaSystemException: ids for this class must be manually assigned before calling save()对应很多异常不可能重试成功的也有。
                //自己改的：查询表最大序号然后加1再插入新记录的情况，无法被捕捉到并发竞争异常，实际上数据库是有抛出异常的，可不是上面那些可抓住！
                //小强数据库会遭遇读写冲突的： 全是有Update Add Delete变更操作的那些接口函数才会运行到这，只有读的操作不会到这。
                //Assert.isTrue("loopWhenProcessIsFinishedSet".equals(pjp.getSignature().getName()),"捕捉函数"+pjp.getSignature().getName());
                Throwable cause = NestedExceptionUtils.getMostSpecificCause(ex);
                if (cause instanceof SQLException) {
                    SQLException sqlException = (SQLException) cause;
                    if ("40001".equals(sqlException.getSQLState())) {   //实际都是一致处理办法: 小强数据库临时错误可以再试
                        backoffMillis=handleTransientException(sqlException, numCalls, pjp.getSignature().toShortString());
                        continue;
                    }
                }
                throw ex;
            }
            catch (CannotCreateTransactionException  ex) {
                //长时间压力测试导致死机一样的结果
                backoffMillis=handleTransientException(ex, numCalls, pjp.getSignature().toShortString());
                continue;
            }
            catch (JpaSystemException ex) {
                //厂家给的是TransientDataAccessException | TransactionSystemException  不可加| JpaSystemException 导致不能抛出异常直接第二尝试当做成功返回。
                //jpa.JpaSystemException: ids for this class must be manually assigned before calling save()对应很多异常不可能重试成功的也有。
                //自己改的：查询表最大序号然后加1再插入新记录的情况，无法被捕捉到并发竞争异常，实际上数据库是有抛出异常的，可不是上面那些可抓住！
                //小强数据库会遭遇读写冲突的： 全是有Update Add Delete变更操作的那些接口函数才会运行到这，只有读的操作不会到这。
                //Assert.isTrue("loopWhenProcessIsFinishedSet".equals(pjp.getSignature().getName()),"捕捉函数"+pjp.getSignature().getName());
                Throwable cause = NestedExceptionUtils.getMostSpecificCause(ex);
                if (cause instanceof SQLException) {
                    SQLException sqlException = (SQLException) cause;
                    if ("40001".equals(sqlException.getSQLState())) {   //实际都是一致处理办法: 小强数据库临时错误可以再试
                        backoffMillis=handleTransientException(sqlException, numCalls, pjp.getSignature().toShortString());
                        logger.debug("JpaSystem重试="+pjp.getSignature().toShortString());
                        continue;
                    }
                }
                throw ex;
            }
            catch ( Exception ex) {
                //很多情况都来这：写冲突报 Unable to commit against JDBC Connection
                //ES爆出异常也能抛给这里的: java.net.SocketTimeoutException: 5,000 milliseconds timeout
                throw ex;
            }
            catch ( Error  ex) {
                //压力测试 风暴 突然像是陷入死循环一样，实际就是CPU负载太高了导致协议类运作异常，看着像等同死机了。死机java.lang.OutOfMemoryError: Java heap space解决方案:调整java启动参数-Xms1g -Xmx5g来增加Heap内存。
               Thread.sleep(500);
               throw ex;
            }
            //并非所有的异常都能被捕捉到，有点直接终结事务处理，跑到事务函数的上一级去抛出错误了。
        } while (numCalls < retryAttempts);
        //函数最多运行30次数，只能抛异常。5分钟为限。总计跨越时间。
        throw new ConcurrencyFailureException("放弃(" + numCalls + ") for method ["
                + pjp.getSignature().toShortString() + "]. Giving up!");
    }

    /**失败后稍微等一下，重复提交事务: 各种错误码有很多，实际上有许多CRDB报错并没有在这里捕捉，会抛出到前端的都会有。
     * 【问题】不知道竞争对手事务运行还需要多长时间会结束啊，以及更多的同样排队一个队伍的事务会执行多久，很可能被插队了。没有优先级。
     * @return long 代表需要继续重试睡眠时间ms。
     * 没考虑pjp.proceed()占用时间！ 有可能大部分时间可能浪费在自己事务的proceed()而不是等待很多竞争事务对手的时间消耗。
     * 【事务重做】实际上当前@Transactional注解函数的全部代码会重新运行一遍的，都白干都得重做，真正排队堵塞在数据库提交阶段。
     * */
    private long handleTransientException(Exception ex, int numCalls, String method) {
        long backoffMillis;      //=Math.min((long) (Math.pow(2, numCalls) + Math.random() * 1000), maxBackoff);  maxBackoff = 15000;
        if(numCalls<=1)     backoffMillis=20;
        else if(numCalls<=2)   backoffMillis=300;
        else if(numCalls<=5)   backoffMillis=200+ (long)(Math.random() * 1000);
        else if(numCalls==6)   backoffMillis=3000;
        else if(numCalls<=10)   backoffMillis=7000;
        else if(numCalls==11)   backoffMillis=4000;
        else if(numCalls<=15)   backoffMillis=8000;
        else if(numCalls==17)   backoffMillis=5000;
        else    backoffMillis=9000;
        //int retryAttempts = 30;
        if (logger.isDebugEnabled()) {
            logger.debug("临时异常 (backoff {}ms) in call {} to '{}': {} runno={}",
                    backoffMillis, numCalls, method, ex.getMessage());
        }
        return backoffMillis;
    }
}



/*
CockroachDB可序列化 SERIALIZABLE 隔离中实现，是SQL标准中最强的隔离级别。
Raft 算法在斯坦福 Diego Ongaro f和 John Ousterhout 于 2013 年发表的《In Search of an Understandable Consensus Algorithm》中提出。相较于 Paxos，
Raft通过逻辑分离使其更容易理解和实现，目前，已经有十多种语言的 Raft 算法实现框架，较为出名的有 etcd、Consul 。
Cockroach 提供快照隔离 (SI) 和 串行快照隔离 (SSI) 语义，此语义允许 外部一致性、无锁读写。 两者都基于历史快照时间戳和当前时间。
SI提供无锁读写但是会有写偏序问题(由于每个事务在更新过程中无法看到其他事务的更改的结果，导致各个事务提交之后的最终结果违反了一致性);
SSI消除了写偏序，但在有争议系统的某些场景下会损失性能。SSI是默认的隔离机制。
 2PC 协议；SI语义事务读操作不会阻塞；SSI语议事务读操作会中止；比两阶段事务提交协议的时延更低。因为第二阶只需要写一次事务表，不需要等待所有事务参与者。
Spanner必须要求精确的时间信号，
所有顶层的元数据保存在一个range里（第一个range）. 一个元数据是256字节，一个64M的range可以保存 2\^18 = 256K个range的元数据，单级寻址可以提供64M * 2\^18 =16T的存储空间。
两级索引 ，第一级用来保存第二级的地址，第二级用来保存数据。 采用两级寻址，支持2\^(18 + 18) = 64G个range, 每个range支持寻址2\^26 B = 64M ，则总共可寻址2\^(36+26) B = 2\^62 B = 4E空间.
the number of nodes dictates the number of heartbeats ； LevelDB/RocksDB是更实用的LSM实现之一；
副本数：默认3个副本。大公司大平台可设为5；
【节点数问题】CockroachDB没有理论上的扩展限制，实际上，它可以在256个节点上实现近似线性的性能。没有节点数限制 #但节点数越多会导致延迟越大。
性能由TPC-C吞吐率衡量，单位是tpmC。tpm是transactions per minute的简称；C指TPC中的C基准程序。是每分钟内系统处理的新订单个数。
官方集群测试=81台CentOS_x86,36vCPU72GB,10Gbps机器,最大tpmC性能=1,684,437个；每节点347订单/秒。
我本机笔记本3个节点服务器 4vCPU2.3Ghz8GBMem+SSD；关键的tpmC=854.3; 磁盘是瓶颈。测试结果比较可信。
DDR5内存实测读取带宽超过90GB/s,写入带宽接近89GB/s,拷贝带宽接近78GB/s。CPU:L3缓存494GB/s,L2缓存987GB/s,L1缓存3422GB/s;内存DRAM随机读写性能损失较小，NVMe SSD存储随机读写性能损失大。
针对数据库来讲，基本都是随机读写的, 此时预计Mem内存比SSD Disk存储速度要快100倍！。
阿里OceanBase以7.07亿tpmC,基于Paxos,1557台节点84vCPU336GB,25Gbps带宽400万PPS；按此比对同等配置Cockroach顶多不超过1.8亿tpmC；对比小强X3.92倍速率？测试基准问题？磁盘、事务波及节点面分区策略。
OceanBase硬件要求8C+32G,NTP同步;主备?备集群个数限制31个;备集群弱一致性读服务;TEXT最大65535字节;主分区具备强一致性读和写能力，备分区具备弱一致性读能力。只读型非Paxos副本;
OceanBase数据库:多版本Read Committed,多级缓存?可靠性？;支持集群节点超过1500台，最大单集群数据量超过3PB; 和Cockroach测试的事务隔离级别不同。
Cockroach数据库：JSONB建议<1MB; 尽量上Inner joins表链接；
#传统的ID自增长顺序Long在分布式系统中无法很好工作,会导致“热点”节点，并造成性能瓶颈。id改成UUID是必须的。id UUID DEFAULT gen_random_uuid();
建议用UUID。而unique_rowid()性能较差最好别用它；递增号码可用单独表计数器事务化手动生成。传统SEQUENCE性能最慢。@SequenceGenerator列不一定是@ID;@GenericGenerator(name = "IDGenerator", strategy = "identity")也类似;
unique_rowid()对应64位不能映射Integer的Java类型要用Long而且不是递增的整数啊！。
计数器实现模式1：要求不出现跳号间隙或漏过某些数值的，性能严重受限：CREATE TABLE cnt(val INT PRIMARY KEY); INSERT INTO cnt(val) VALUES(1); INSERT INTO cnt(val) SELECT max(val)+1 FROM cnt RETURNING val;的模式。
计数器实现模式2：允许出现漏掉某些数字号码，跳号可以被容忍的情况：SEQUENCE也会有调号。更新事务对某个计数器字段直接 counter++；事务保证唯一性获取编号给用户。事务回滚的？
快照隔离（SI,Snapshot Isolation）：MVCC和锁都是SI的重要实现手段，时间戳是生成SI的关键要素。SI还无法解决Write Skew写偏序; 分布式系统一时钟就成为一个复杂问题;
完全的串行化隔离（Serializable Isolation）=S2PL模式Strict Two-Phase Locking无法应用于实际生产环境。直到SSI的出现，人们终于找到具有实际价值的串行化隔离方案。SSI, Serializable Snapshot Isolation序列化快照是基于SI改进达到Serializable级别的隔离性。
SSI保留了SI的很多优点，特别是读不阻塞任何操作，写不会阻塞读。事务依然在快照中运行，但增加了对事务间读写冲突的监控用于识别事务图（transaction graph）中的危险结构。
虽然会导致某些事务的错误回滚（不会导致anomaly的事务被误杀），但可确保消除anomaly。    SSI性能接近SI，远远好于S2PL模式。
CockroachDB实现SSI并将其作为其默认隔离级别。串行化快照隔离SSI带来的好处：无需开发人员在代码通过#显式锁#来避免异常，从而降低了人为错误的概率。
console联机操作CockroachDB翻页滚动也会抛出异常，要手写offset limit,排序深度翻页性能越深的越差。长时事务批处理作业?: Checkpoint? Cursor?
CRDB的SSI探秘：插入新记录会串行就会阻碍它人读取请求，但是更新旧的记录却不会阻碍它人读取请求，但问题是更新记录速度更慢，插入新记录速度更快的。OLTP更新多于插入吧，为读取优化。
插入新记录的SSI事务并不会阻碍它人的以ID为定位的findById()请求; 实体表若是属于插入新记录很频繁的表，应该尽量避免用不是ID的字段来做过滤查找的？带有插入新记录的事务必须尽量短暂必须小于10秒；
只要有生成实体新记录的接口函数都应当尽量急速提交，事务时间长了就会阻碍它人的对新记录对应的那一个实体表查询的过滤请求；尽量业务层面分解事务函数，能拆解的就拆解，事务小型化；业务层弥补合并和事务外失败逻辑。
针对CockroachDB优化：尽量是Update,少用Insert Delete,事务Insert会阻碍其他人除了ID查询之外其余查询过滤统计等操作。Update语句就不会有这个阻塞并发的问题，但是Update比Insert执行更慢点。
数据库Vitess 支持单个分片内的事务，不支持跨分片事务。 数据库TiDB不支持外键,不支持串行化隔离级别;
YugaByte数据库是对手        https://copyfuture.com/blogs-details/20210727213025980v
cockroachDB的解密    https://www.zhihu.com/question/429946482/answer/1602639985
YugaByte的分布式事务原子性实现 与cockroachDB 旧版本类似, 都是2PC 变种，只是cockroachDB 新版本做了优化，变成了1PC ,性能估计是目前所有开源New-SQL 的好几倍, 后期YugaByte也会跟进优化,用C++实现;
CockroachDB和YugabyteDB交锋,TiDB类似  https://www.zhihu.com/question/449949351
分布式数据库 叫法=NewSQL;   https://baijiahao.baidu.com/s?id=1699078374020499984&wfr=spider&for=pc
加version乐观锁实际对CRDB没用吗?? CRDB数据库的内置事务隔离等级更高。IDEA的数据库工具会无意识添加事务很可能导致后端程序事务超慢或失败，必须重启动IDEA。
IDEA Database工具通过console当前数据库，跨数据库查询另外一个数据库的表的情况，可能死锁？ 同一个数据库之内的事务没有这种问题。
CockroachDB 数据分区 GEO-Partitioning 同城双机房 3地多中心 https://www.sohu.com/a/326958845_411876
BACKUP RESTORE 集群备份恢复功能 https://www.cockroachlabs.com/docs/v21.2/backup
Backup and restore with userfile没用云的不能搞集群级备份 https://www.cockroachlabs.com/docs/v21.2/use-userfile-for-bulk-operations#backup-and-restore-with-userfile
集群证书权限用户角色等设置是属于集群级别备份，其它都是database级别备份多个库多个命令文件Job。cockroach userfile get; upload; cockroach userfile delete;
ZNBase(基于 CockroachDB 来) 和 TiDB 都是既有行存的数据副本，也有列存的数据副本; TiDB不支持外键；
临时备份: INSERT INTO isp_saved1 SELECT * FROM isp;
drop table isp cascade;只能删除isp却无法把eqp关联的字段清空所以后续报错！
性能严重影响：https://www.cockroachlabs.com/docs/stable/delete.html 非全表删除情况：CRDB数据库删除数据性能考虑： 过滤字段必须建立索引。可重做的小事务分批次受限大小的记录数删除。最好排序获取ID范围，例子如下：
 DELETE  from public.storesync where fail='OK' AND id<='20dbf4b0-a0d5-4be1-bd85-43dc4c4fbb2f'
 ORDER BY id DESC LIMIT 5000  RETURNING id,cod,oid,fail;  修改id<=''取值，多次执行删除，最后检查是否条件满足所有的记录都已删除。
免费版: CRDB非enterprise license版 PARTITION BY无法做: Zone Multi-region功能；
在SELECT查询返回的主键值上以小于初始SELECT批大小的批编写嵌套的DELETE循环。如果可能,建议每个循环要开启新的单独的事务中执行独立一次DELETE;把Select独立于Delete事务之外的先前查询。
人工拆解事务：成了用户负责的。实际是丢失大的事务特性，只是保障了小事务！最好带上条件select Order by ID主键，看似矛盾：最好合并多个delete为单一条delete语句 in[ids..]。
批量非独立定位的有索引的情形做法：DELETE FROM new_order WHERE no_w_id <= %s ORDER BY no_w_id DESC LIMIT 5000 RETURNING no_w_id；最好被过滤列必须有索引！
?单一个事务中：前面删除了某id，后面继续查询该表和再做删除其他id，性能问题。
删除性能差：批量数据删除过滤字段有索引，过滤字段没索引的2个情景；另外还有JPA复杂关联删除id关联多个表的情况也会一次性批量删除List集合关联的，需要关联ID都做外键约束带上索引。
CRDB的core版本； Liquibase数据库变更版本管理Spring集成、Gradle插件； JOOQ适合查询？=QueryDsl配套JPA对比 | JPA Specification toPredicate CriteriaBuilder；
* */
